home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Visual Cafe 3
/
Visual Cafe 3.ISO
/
Vcafe
/
JFC.bin
/
LabelView.java
< prev
next >
Wrap
Text File
|
1998-06-30
|
24KB
|
714 lines
/*
* @(#)LabelView.java 1.38 98/04/09
*
* Copyright (c) 1997 Sun Microsystems, Inc. All Rights Reserved.
*
* This software is the confidential and proprietary information of Sun
* Microsystems, Inc. ("Confidential Information"). You shall not
* disclose such Confidential Information and shall use it only in
* accordance with the terms of the license agreement you entered into
* with Sun.
*
* SUN MAKES NO REPRESENTATIONS OR WARRANTIES ABOUT THE SUITABILITY OF THE
* SOFTWARE, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
* PURPOSE, OR NON-INFRINGEMENT. SUN SHALL NOT BE LIABLE FOR ANY DAMAGES
* SUFFERED BY LICENSEE AS A RESULT OF USING, MODIFYING OR DISTRIBUTING
* THIS SOFTWARE OR ITS DERIVATIVES.
*
*/
package com.sun.java.swing.text;
import java.awt.*;
import com.sun.java.swing.event.*;
/**
* A LabelView is a styled chunk of text that represents a view
* mapped over an element in the text model. The view supports breaking
* for the purpose of formatting. The fragments produced
* by breaking share the view that has primary responsibility
* for the element (i.e. they are nested classes and carry only
* a small amount of state of their own) so they can share its
* resources.
* <p>
* This view is generally responsible for displaying character
* level attributes in some way. Since this view represents
* text that may have tabs embedded in it, it implements the
* <code>TabableView</code> interface. Tabs will only be
* expanded if this view is embedded in a container that does
* tab expansion. ParagraphView is an example of a container
* that does tab expansion.
*
* @author Timothy Prinzing
* @version 1.38 04/09/98
*/
public class LabelView extends View implements TabableView {
/**
* Constructs a new view wrapped on an element.
*
* @param elem the element
*/
public LabelView(Element elem) {
super(elem);
text = new Segment();
}
/**
* Load the text buffer with the given range
* of text. This is used by the fragments
* broken off of this view as well as this
* view itself.
*/
final void loadText(int p0, int p1) {
try {
Document doc = getDocument();
doc.getText(p0, p1 - p0, text);
if (rightToLeft) {
// PENDING(Java2D) use real rendering with shaping
char[] reversed = new char[p1 - p0];
int i = 0;
for (int offs = text.offset + text.count - 1; offs >= text.offset; offs--) {
reversed[i++] = text.array[offs];
}
text.array = reversed;
text.offset = 0;
}
} catch (BadLocationException bl) {
throw new StateInvariantError("LabelView: Stale view: " + bl);
}
}
/**
* Paint the given range. This is used by the
* fragments broken off of this view as well as this
* view itself.
*/
final void paintText(Graphics g, Shape a, int p0, int p1, boolean stripWhitespace) {
Rectangle alloc = a.getBounds();
sync();
loadText(p0, p1);
int y = alloc.y + alloc.height - metrics.getDescent();
g.setFont(font);
g.setColor(fg);
Utilities.drawTabbedText(text, alloc.x, y, g, expander, p0);
if (underline) {
if (stripWhitespace) {
while ((text.count > 0) && (Character.isWhitespace(text.array[text.count-1]))) {
alloc.width -= metrics.charWidth(text.array[text.count-1]);
text.count -= 1;
}
}
y += 1;
g.drawLine(alloc.x, y, alloc.x + alloc.width, y);
}
}
/**
* Synchronize the view's cached values with the model.
* This causes the font, metrics, color, etc to be
* recached if the cache has been invalidated.
*/
final void sync() {
if (font == null) {
Element e = getElement();
Document d = getDocument();
if (d instanceof StyledDocument) {
StyledDocument doc = (StyledDocument) d;
AttributeSet attr = e.getAttributes();
font = doc.getFont(attr);
fg = doc.getForeground(attr);
underline = StyleConstants.isUnderline(attr);
metrics = Toolkit.getDefaultToolkit().getFontMetrics(font);
} else {
throw new StateInvariantError("LabelView needs StyledDocument");
}
}
}
/**
* Determines the preferred span for this view along an
* axis. This is shared by the broken views.
*
* @param axis may be either X_AXIS or Y_AXIS
* @param x the location to calculate the span from.
* @returns the span the view would like to be rendered into.
* Typically the view is told to render into the span
* that is returned, although there is no guarantee.
* The parent may choose to resize or break the view.
*/
final float getPreferredSpan(int axis, int p0, int p1, int x) {
sync();
switch (axis) {
case View.X_AXIS:
loadText(p0, p1);
int width = Utilities.getTabbedTextWidth(text, metrics, x, expander, p0);
return Math.max(width, 1);
case View.Y_AXIS:
return metrics.getHeight();
default:
throw new IllegalArgumentException("Invalid axis: " + axis);
}
}
/**
* Provides a mapping from the document model coordinate space
* to the coordinate space of the view mapped to it.
* This is shared by the broken views.
*
* @param pos the position to convert
* @param a the allocated region to render into
* @return the bounding box of the given position
* @exception BadLocationException if the given position does not represent a
* valid location in the associated document
* @see View#modelToView
*/
Shape modelToView(int pos, Shape a, int p0, int p1) throws BadLocationException {
Rectangle alloc = a.getBounds();
if ((pos >= p0) && (pos <= p1)) {
// determine range to the left of the position
loadText(p0, pos);
sync();
int width = Utilities.getTabbedTextWidth(text, metrics, alloc.x, expander, p0);
if (rightToLeft) {
// PENDING(Java2D) use real rendering with shaping
return new Rectangle(alloc.x + alloc.width - width,
alloc.y, 0, metrics.getHeight());
}
return new Rectangle(alloc.x + width, alloc.y, 0, metrics.getHeight());
}
throw new BadLocationException("modelToView - can't convert", p1);
}
/**
* Provides a mapping from the view coordinate space to the logical
* coordinate space of the model.
*
* @param x the X coordinate
* @param y the Y coordinate
* @param a the allocated region to render into
* @return the location within the model that best represents the
* given point of view
* @see View#viewToModel
*/
int viewToModel(float x, float y, Shape a, int p0, int p1) {
Rectangle alloc = a.getBounds();
sync();
loadText(p0, p1);
int offs = Utilities.getTabbedTextOffset(text, metrics,
alloc.x, (int) x, expander, p0);
if (rightToLeft) {
// PENDING(Java2D) use real rendering with shaping
return p1 - offs;
}
return p0 + offs;
}
int getBreakWeight(int axis, float pos, float len, int p0, int p1) {
if (axis == View.X_AXIS) {
sync();
loadText(p0, p1);
int index = Utilities.getTabbedTextOffset(text, metrics,
(int)pos, (int)(pos+len),
expander, p0);
if (index == 0) {
// break is at the start offset
return BadBreakWeight;
}
for (int i = text.offset + Math.min(index, text.count - 1);
i >= text.offset; i--) {
char ch = text.array[i];
if (Character.isWhitespace(ch)) {
// found whitespace
return ExcellentBreakWeight;
}
}
// no whitespace
return GoodBreakWeight;
}
return super.getBreakWeight(axis, pos, len);
}
// --- TabableView methods --------------------------------------
/**
* Determines the desired span when using the given
* tab expansion implementation.
*
* @param x the position the view would be located
* at for the purpose of tab expansion >= 0.
* @param e how to expand the tabs when encountered.
* @return the desired span >= 0
* @see TabableView#getTabbedSpan
*/
public float getTabbedSpan(float x, TabExpander e) {
expander = e;
this.x = (int) x;
return getPreferredSpan(X_AXIS, getStartOffset(), getEndOffset(), this.x);
}
/**
* Determines the span along the same axis as tab
* expansion for a portion of the view. This is
* intended for use by the TabExpander for cases
* where the tab expansion involves aligning the
* portion of text that doesn't have whitespace
* relative to the tab stop. There is therefore
* an assumption that the range given does not
* contain tabs.
* <p>
* This method can be called while servicing the
* getTabbedSpan or getPreferredSize. It has to
* arrange for its own text buffer to make the
* measurements.
*
* @param p0 the starting document offset >= 0
* @param p1 the ending document offset >= p0
* @return the span >= 0
*/
public float getPartialSpan(int p0, int p1) {
// PENDING should probably use a static buffer since there
// should be only one thread accessing it.
sync();
int width = 0;
try {
Segment s = new Segment();
getDocument().getText(p0, p1 - p0, s);
width = Utilities.getTabbedTextWidth(s, metrics, x, expander, p0);
} catch (BadLocationException bl) {
}
return width;
}
// --- View methods ---------------------------------------------
/**
* Renders a portion of a text style run.
*
* @param g the rendering surface to use
* @param a the allocated region to render into
*/
public void paint(Graphics g, Shape a) {
paintText(g, a, getStartOffset(), getEndOffset(), false);
}
/**
* Determines the preferred span for this view along an
* axis.
*
* @param axis may be either View.X_AXIS or View.Y_AXIS
* @returns the span the view would like to be rendered into >= 0.
* Typically the view is told to render into the span
* that is returned, although there is no guarantee.
* The parent may choose to resize or break the view.
*/
public float getPreferredSpan(int axis) {
return getPreferredSpan(axis, getStartOffset(), getEndOffset(), this.x);
}
/**
* Determines the desired alignment for this view along an
* axis. For the label, the alignment is along the font
* baseline for the y axis, and the superclasses alignment
* along the x axis.
*
* @param axis may be either View.X_AXIS or View.Y_AXIS
* @returns the desired alignment. This should be a value
* between 0.0 and 1.0 inclusive, where 0 indicates alignment at the
* origin and 1.0 indicates alignment to the full span
* away from the origin. An alignment of 0.5 would be the
* center of the view.
*/
public float getAlignment(int axis) {
if (axis == View.Y_AXIS) {
float h = metrics.getHeight();
float d = metrics.getDescent();
float align = (h - d) / h;
return align;
}
return super.getAlignment(axis);
}
/**
* Provides a mapping from the document model coordinate space
* to the coordinate space of the view mapped to it.
*
* @param pos the position to convert >= 0
* @param a the allocated region to render into
* @return the bounding box of the given position
* @exception BadLocationException if the given position does not represent a
* valid location in the associated document
* @see View#modelToView
*/
public Shape modelToView(int pos, Shape a) throws BadLocationException {
return modelToView(pos, a, getStartOffset(), getEndOffset());
}
/**
* Provides a mapping from the view coordinate space to the logical
* coordinate space of the model.
*
* @param x the X coordinate >= 0
* @param y the Y coordinate >= 0
* @param a the allocated region to render into
* @return the location within the model that best represents the
* given point of view >= 0
* @see View#viewToModel
*/
public int viewToModel(float x, float y, Shape a) {
return viewToModel(x, y, a, getStartOffset(), getEndOffset());
}
/**
* Gives notification from the document that attributes were changed
* in a location that this view is responsible for.
*
* @param e the change information from the associated document
* @param a the current allocation of the view
* @param f the factory to use to rebuild if the view has children
* @see View#changedUpdate
*/
public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
font = null;
}
/**
* Determines how attractive a break opportunity in
* this view is. This can be used for determining which
* view is the most attractive to call <code>breakView</code>
* on in the process of formatting. The
* higher the weight, the more attractive the break. A
* value equal to or lower than <code>View.BadBreakWeight</code>
* should not be considered for a break. A value greater
* than or equal to <code>View.ForcedBreakWeight</code> should
* be broken.
* <p>
* This is implemented to forward to the superclass for
* the Y_AXIS. Along the X_AXIS the following values
* may be returned.
* <dl>
* <dt><b>View.ExcellentBreakWeight</b>
* <dd>if there is whitespace proceeding the desired break
* location.
* <dt><b>View.BadBreakWeight</b>
* <dd>if the desired break location results in a break
* location of the starting offset.
* <dt><b>View.GoodBreakWeight</b>
* <dd>if the other conditions don't occur.
* </dl>
* This will normally result in the behavior of breaking
* on a whitespace location if one can be found, otherwise
* breaking between characters.
*
* @param axis may be either View.X_AXIS or View.Y_AXIS
* @param pos the potential location of the start of the
* broken view >= 0. This may be useful for calculating tab
* positions.
* @param len specifies the relative length from <em>pos</em>
* where a potential break is desired >= 0.
* @return the weight, which should be a value between
* View.ForcedBreakWeight and View.BadBreakWeight.
* @see LabelView
* @see ParagraphView
* @see BadBreakWeight
* @see GoodBreakWeight
* @see ExcellentBreakWeight
* @see ForcedBreakWeight
*/
public int getBreakWeight(int axis, float pos, float len) {
return getBreakWeight(axis, pos, len, getStartOffset(), getEndOffset());
}
/**
* Breaks this view on the given axis at the given length.
* This is implemented to attempt to break on a whitespace
* location, and returns a fragment with the whitespace at
* the end. If a whitespace location can't be found, the
* nearest character is used.
*
* @param axis may be either View.X_AXIS or View.Y_AXIS
* @param p0 the location in the model where the
* fragment should start it's representation >= 0.
* @param pos the position along the axis that the
* broken view would occupy >= 0. This may be useful for
* things like tab calculations.
* @param len specifies the distance along the axis
* where a potential break is desired >= 0.
* @return the fragment of the view that represents the
* given span, if the view can be broken. If the view
* doesn't support breaking behavior, the view itself is
* returned.
* @see View#breakView
*/
public View breakView(int axis, int p0, float pos, float len) {
if (axis == View.X_AXIS) {
sync();
loadText(p0, getEndOffset());
int index = Utilities.getTabbedTextOffset(text, metrics,
(int)pos, (int)(pos+len),
expander, p0);
for (int i = text.offset + Math.min(index, text.count - 1);
i >= text.offset; i--) {
char ch = text.array[i];
if (Character.isWhitespace(ch)) {
// found whitespace, break here
index = i - text.offset + 1;
break;
}
}
int p1 = p0 + index;
LabelFragment frag = new LabelFragment(getElement(), p0, p1);
frag.x = (int) pos;
return frag;
}
return this;
}
/**
* Creates a view that represents a portion of the element.
* This is potentially useful during formatting operations
* for taking measurements of fragments of the view. If
* the view doesn't support fragmenting (the default), it
* should return itself.
* <p>
* This view does support fragmenting. It is implemented
* to return a nested class that shares state in this view
* representing only a portion of the view.
*
* @param p0 the starting offset >= 0. This should be a value
* greater or equal to the element starting offset and
* less than the element ending offset.
* @param p1 the ending offset > p0. This should be a value
* less than or equal to the elements end offset and
* greater than the elements starting offset.
* @returns the view fragment, or itself if the view doesn't
* support breaking into fragments.
* @see LabelView
*/
public View createFragment(int p0, int p1) {
Element elem = getElement();
return new LabelFragment(elem, p0, p1);
}
// --- variables ------------------------------------------------
Font font;
FontMetrics metrics;
Color fg;
Segment text;
boolean underline;
boolean rightToLeft;
/**
* how to expand tabs
*/
TabExpander expander;
/**
* location for determining tab expansion against.
*/
int x;
/**
* A label that represents only a portion of a character
* style run element. This carries very little state
* of its own and depends heavily upon the outer class.
*/
class LabelFragment extends View implements TabableView {
/**
* Constructs a new view wrapped on an element.
*
* @param elem the element
* @param p0 the beginning of the range
* @param p1 the end of the range
*/
public LabelFragment(Element elem, int p0, int p1) {
super(elem);
offset = (short) (p0 - elem.getStartOffset());
length = (short) (p1 - p0);
}
// --- TabableView methods --------------------------------------
/**
* Determines the desired span when using the given
* tab expansion implementation.
*
* @param x the position the view would be located
* at for the purpose of tab expansion >= 0.
* @param e how to expand the tabs when encountered.
* @return the desired span >= 0
* @see TabableView#getTabbedSpan
*/
public float getTabbedSpan(float x, TabExpander e) {
LabelView.this.expander = e;
this.x = (int) x;
return LabelView.this.getPreferredSpan(X_AXIS, getStartOffset(),
getEndOffset(), this.x);
}
/**
* Determine the span along the same axis as tab
* expansion for a portion of the view. This is
* intended for use by the TabExpander for cases
* where the tab expansion involves aligning the
* portion of text that doesn't have whitespace
* relative to the tab stop. There is therefore
* an assumption that the range given does not
* contain tabs.
*/
public float getPartialSpan(int p0, int p1) {
return LabelView.this.getPartialSpan(p0, p1);
}
// --- View methods ----------------------------
/**
* Fetches the portion of the model that this view is responsible for.
*
* @return the starting offset into the model
* @see View#getStartOffset
*/
public int getStartOffset() {
Element e = getElement();
return e.getStartOffset() + offset;
}
/**
* Fetches the portion of the model that this view is responsible for.
*
* @return the ending offset into the model
* @see View#getEndOffset
*/
public int getEndOffset() {
Element e = getElement();
return e.getStartOffset() + offset + length;
}
/**
* Renders a portion of a text style run.
*
* @param g the rendering surface to use
* @param a the allocated region to render into
*/
public void paint(Graphics g, Shape a) {
paintText(g, a, getStartOffset(), getEndOffset(), true);
}
/**
* Determines the preferred span for this view along an
* axis.
*
* @param axis may be either X_AXIS or Y_AXIS
* @returns the span the view would like to be rendered into.
* Typically the view is told to render into the span
* that is returned, although there is no guarantee.
* The parent may choose to resize or break the view.
*/
public float getPreferredSpan(int axis) {
return LabelView.this.getPreferredSpan(axis, getStartOffset(), getEndOffset(), this.x);
}
/**
* Determines the desired alignment for this view along an
* axis. For the label, the alignment is along the font
* baseline for the y axis, and the superclasses alignment
* along the x axis.
*
* @param axis may be either X_AXIS or Y_AXIS
* @returns the desired alignment. This should be a value
* between 0.0 and 1.0 where 0 indicates alignment at the
* origin and 1.0 indicates alignment to the full span
* away from the origin. An alignment of 0.5 would be the
* center of the view.
*/
public float getAlignment(int axis) {
return LabelView.this.getAlignment(axis);
}
/**
* Provides a mapping from the document model coordinate space
* to the coordinate space of the view mapped to it.
*
* @param pos the position to convert
* @param a the allocated region to render into
* @return the bounding box of the given position
* @exception BadLocationException if the given position does not represent a
* valid location in the associated document
* @see View#modelToView
*/
public Shape modelToView(int pos, Shape a) throws BadLocationException {
return LabelView.this.modelToView(pos, a, getStartOffset(), getEndOffset());
}
/**
* Provides a mapping from the view coordinate space to the logical
* coordinate space of the model.
*
* @param x the X coordinate
* @param y the Y coordinate
* @param a the allocated region to render into
* @return the location within the model that best represents the
* given point of view
* @see View#viewToModel
*/
public int viewToModel(float x, float y, Shape a) {
return LabelView.this.viewToModel(x, y, a, getStartOffset(), getEndOffset());
}
/**
* Gives notification from the document that attributes were changed
* in a location that this view is responsible for.
*
* @param e the change information from the associated document
* @param a the current allocation of the view
* @param f the factory to use to rebuild if the view has children
* @see View#changedUpdate
*/
public void changedUpdate(DocumentEvent e, Shape a, ViewFactory f) {
LabelView.this.changedUpdate(e, a, f);
}
/**
* @see LabelView#getBreakWeight
*/
public int getBreakWeight(int axis, float x, float len) {
return LabelView.this.getBreakWeight(axis, x, len,
getStartOffset(), getEndOffset());
}
/**
* Breaks this view on the given axis at the given length.
*
* @param axis may be either X_AXIS or Y_AXIS
* @param offset the location in the model where the
* fragment should start it's representation.
* @param pos the position along the axis that the
* broken view would occupy. This may be useful for
* things like tab calculations.
* @param len specifies the distance along the axis
* where a potential break is desired.
* @param a the current allocation of the view
* @return the fragment of the view that represents the
* given span, if the view can be broken. If the view
* doesn't support breaking behavior, the view itself is
* returned.
* @see View#breakView
*/
public View breakView(int axis, int offset, float pos, float len) {
return LabelView.this.breakView(axis, offset, pos, len);
}
// ---- variables ---------------------------------
short offset;
short length;
int x;
}
}